不吹不黑,聊一聊从 Echarts 到炫酷大屏图表的距离

您所在的位置:网站首页 echarts 仪表 不吹不黑,聊一聊从 Echarts 到炫酷大屏图表的距离

不吹不黑,聊一聊从 Echarts 到炫酷大屏图表的距离

2023-03-24 22:53| 来源: 网络整理| 查看: 265

先来一张示例集合,感受一下 Echarts 经典图表和大屏图表的“距离”。

Echarts 经典图表大屏图表

(Tips:深入图表本身的细节观察,你会发现很多不一样)

两种图表集合一一对应,但经过我们的设计师一番精心设计打磨之后,差别却非常大。那么后者实现起来有多难呢?经过实践我们得出的答案是:“可千里也可毫厘,距离几何,全看自己!

千里是为何?

如果你没有数学几何基础,光 Echarts 的配置都能把你难哭(不是黑,专业工具确实需要专业基础)。

毫厘又是为何?

如果你熟悉各种坐标系,对函数变换和视觉编码比较熟悉,那很容易实现,但是美化和细节精调依旧需要花费功夫。

接下来会依次对六个图表进行剖析,来讲解具体的实现(配置)过程。

1、从最简单的玫瑰图开始说起

玫瑰图

看到这个图的第一反应是这是一个加了背景并限制了起止角度的饼图,实现起来也比较容易,在构造 Echarts 配置项中需要注意三点:

数据大小映射到外半径 整体的起始角度限制在180度以内背景渐变

在这个示例中 series 共有两个,一是用以展现数据的序列,二是用来展现背景的序列。将所有数据的角度限制在180度范围之内,为了达到这个目的,我们对传入序列的数据使用空数据进行了扩充。空数据充当占位的角色,格式如下:

const placeholder = { value: 0, itemStyle: { opacity: 0 }, label: { show: false }, labelLine: { show: false } } // 占位数据的个数与实际要展示的数据格式一致 const seriesForData = { roseType: "area", type: "pie", z: 5, data: originData.concat([placeholder, ...]), // placeholder个数等于原始数据长度 center: ['10%', '50%'], radius: ['10%', '50%'], label: 'label 配置(太长不贴)', labelLine: 'labelLine 配置(太长不贴)', }

用来展现背景的序列也用占位方法区分左右半圆,需要注意的是要设置背景的渐变,使用径向渐变即可:

const seriesForBg = { type: 'pie', z: 2, center: ['10%', '50%'], radius: ['5%', '60%'], silent: true, data: [{ value: 1, itemStyle: { normal: { color: { // 设置背景渐变 type: 'radial', x: 0.1, y: 0.5, r: 1, colorStops: [{ offset: 0, color: "rgb(36,52,67)" }, { offset: 1, color: "rgb(12,20,28, 0.5)" }] } } } }, placeholder] }

再加上颜色等配置就可以生成最终的 options:

const options = { animation: false, backgroundColor: '#010105', color: ["#FE0404", "#FBA200", "#00CEFF", "#00DC58"], series:[ seriesForData, seriesForBg ] }

详细的配置和运行示例可以参考 在线示例

2、条形图(极坐标)

极坐标系下的条形图

与玫瑰图相同的是,这个条形图也有背景,同理,基本思路也通过构造一个序列用来展示背景。首先假设数据格式如下:

const color = ['#90BCDE', '#A451F1', '#635BC1', '#2B62B1', '#3B478D'] const data = [{ name: '网络利用率', value: 0.12 }, { name: '显存利用率', value: 0.23 }, { name: '显卡利用率', value: 0.34 },{ name: '内存利用率', value: 0.45 }, { name: 'CPU利用率', value: 0.73 }]

在这个图中,需要注意的是数据的顺序以及数据最大值,这里使用小数点,如果 value 为 1 则表示柱子充满 360 度。根据数据可以分别生成表示背景和数据的序列配置:

// 表示背景的 series const seriesForBg = { type: 'bar', name: 'bg', barCategoryGap:'50%', animation: false, data: data.map((d) => { return { name: d.name, value: 1, itemStyle: { color: 'rgba(27,36,62,0.5)' } } }), coordinateSystem: 'polar', slient: true, center: ['65%', '50%'] } // 表示数据的 series const seriesForData = { type: 'bar', name: 'value', barGap: '-100%', barCategoryGap:'50%', data: data.map((d, i) => { return { name: d.name, value: d.value, itemStyle: { color: color[i] } } }), coordinateSystem: 'polar', slient: true, center: ['65%', '50%'] }

然后再对标签和标签线进行配置,最后生成 options 传给 Echarts 即可:

const options = { angleAxis: '略', radiusAxis: '略', series: [ seriesForBg, seriesForData ] }

详细的配置和运行示例可以参考 在线示例

3、条形图

条形图

这个图中比较特殊的有个部分,一是整个图的背景渐变,二是每个条形图顶部的圆点标记。

背景渐变和每个条形图的黑色背景可以使用同一个序列生成,主要思路是根据数据大小为每个条形图设置固定高度的黑色背景,同时设置其背景阴影:

const data =[{ year: '2013', value: Math.round(Math.random() * 4000) }, { year: '2014', value: Math.round(Math.random() * 4000) }, { year: '2015', value: Math.round(Math.random() * 4000) }, { year: '2016', value: Math.round(Math.random() * 4000) }, { year: '2017', value: Math.round(Math.random() * 4000) }] const maxValue = 4000 const seriesForBg = { name: 'background', type: 'bar', animation: false, silent: true, data: data.map((d) => { return { value: maxValue, itemStyle: { barBorderRadius: [25, 25, 0, 0], color: 'rgba(0,0,0,1)', shadowColor: 'rgba(255, 127, 0, 0.7)', shadowBlur: 1000, shadowOffsetY: -3, borderColor: 'rgba(255, 127, 0, 0.3)', } } }) }

然后是表示数据的序列配置:

const seriesForData = { name: 'realBar', type: 'custom', renderItem: renderItem, data: data } // 自定义渲染方式 function renderItem (params, api) { let topCenter = api.coord([api.value(0), api.value(1)]) let width = api.size([1, 0])[0] * api.value(2) / 100 let height = api.size([1, api.value(1)])[1] return { type: 'group', children: [{ type: 'rect', shape: { x: topCenter[0] - width * 0.8, y: topCenter[1], width: width * 1.6, height: height }, style: api.style() }, { type: 'circle', shape: { cx: topCenter[0], cy: topCenter[1], r: width * 1.6, }, // style: api.style(), style: api.style({ fill: '#FEC' }) }, { type: 'circle', shape: { cx: topCenter[0], cy: topCenter[1], r: width * 0.8, }, style: api.style({ fill: 'rgba(255, 64, 0, 1)' }) }], } }

最后生成 Echarts 的 options 即可,详细的配置和运行示例可以参考 在线示例

4、圆饼图

圆饼图

这个图的主要难点在怎么绘制出完美的渐变效果。在 Canvas 中绘制颜色渐变时,给的接口比较简单,也就是给定两个 (0, 0) 至 (1, 1) 所包含的点,让其作为颜色渐变方向的起点和终点,然后在绘制的时候会自动的填充渐变颜色。但是我们在实际的使用场景时很难取得这样 (0, 0) 至 (1, 1) 正方形范围内的坐标,也有很多场景不是凑巧就是横向颜色渐变或者纵向颜色渐变。因此需要进行一定的转换。

转换过程可以分为以下四步:

得到每一段弧的从起点到终点的向量(注意方向),平移这个向量,使其中心点移动到(0.5, 0.5)上提取每段弧的向量计算向量和 [ (0,0) (1,1) ] 单位正方形边框的交点,如果向量不够长就延长至能产生交点计算向量和正方形边框的交点计算这两个交点的坐标,可以简单套用以下这个公式和代码交点坐标示意图function normalize(posi) { let dx = posi[2] - posi[0] let dy = posi[3] - posi[1] let tanV = dx / dy let cotV = dy / dx if (Math.abs(tanV) 0) { if (dx > 0) { return [0.5 - tanV / 2, 0, 0.5 + tanV / 2, 1] } else { return [0.5 + tanV / 2, 1, 0.5 - tanV / 2, 0] } } else { if (dx > 0) { return [0.5 + tanV / 2, 1, 0.5 - tanV / 2, 0] } else { return [0.5 - tanV / 2, 0, 0.5 + tanV / 2, 1] } } } else { if (cotV > 0) { if (dx > 0) { return [0, 0.5 - cotV / 2, 1, 0.5 + cotV / 2] } else { return [1, 0.5 + cotV / 2, 0, 0.5 - cotV / 2] } } else { if (dx > 0) { return [0, 0.5 - cotV / 2, 1, 0.5 + cotV / 2] } else { return [1, 0.5 + cotV / 2, 0, 0.5 - cotV / 2] } } } } 使用算好的边框上的两个交点去设置颜色渐变

详细计算过程和示例代码可以参考 在线示例

5、柱状图(极坐标)

柱状图

这个图其实是一个柱状图的变体,其功能类似于一个放置在极坐标下的柱状图(玫瑰图)

玫瑰图和柱状图

从功能上而言其实是一样的,但是美观程度嘛,就上升了不止一个数量级。首先,我们先来看“柱子”,常见的柱子,都是一个矩形,而此处的柱子是使用的多条“凸曲线”拟合的,比如“正弦曲线”:

y = sin( x )

但是“正弦曲线”存在着负值,而“柱子”的高度是不可能为负数的,那么怎么办呢,有两种方法,可以将正弦曲线平移为:

y = sin( x ) + 1

或者使用正弦函数的平方:

y = sin( x ) * sin( x )

在这里我们选择的是后者。当然,在选择“凸曲线”的过程中,我们还尝试了:

y = 2 * abs( x - floor( x ) - 0.5 )

其效果如下,不如正弦曲线平滑,对比如下图:

正弦曲线非正弦曲线

选择好“凸曲线”后,我们使用 line 来模拟柱子。但是在绘制 line 之前,我们需要计算出 line 中的点,即对每条 line 进行采样,单条线是无法模拟出柱子的效果的,单条先模拟出来的是趋势图,在给出的示例代码中,我们使用的是 12 条 line,只有最外层的那一条才会达到柱子的真实高度,其余的都会等比例进行缩放,采样精度和过程如下:

不同线条数模拟柱子的效果(1条,5条,12条)

每一根柱子上的每一条线都会进行20次采样,如果这个数字设置的过低,会使柱子看起来不平滑,如下图从左到右采样精度逐渐提高:

每条线在每根柱子上的采样精度(5,10,20)

采样部分核心代码如下:

const size = 12 // 有多少条数据 const lineSize = 12 // 用多少跟线来模拟 const tickTime = 20 // 精度 const valueMax = 200 const lines = [] function likeSin(val) { const tail = val - Math.floor(val) return tail < 0.5 ? tail : 1 - tail } function ratio(val, A, B) { const k = Math.sin(val * Math.PI) return A * k + B * (1 - k) } for (let j = 0; j < tickTime; ++j) { const yValue = ratio(j / tickTime, Math.pow(Math.sin(tick * Math.PI), 2), likeSin(tick)) * value / 2 for (let k = 0; k < lineSize; ++k) { lines[k].push([yValue * (k + 1) / lineSize + valueMax / 3, tick - 0.5]) } tick = i + j / tickTime } // 每条线首尾相连 for (let k = 0; k < lineSize; ++k) { lines[k].push(lines[k][0]) }

然后生成 series 配置:

const series = [] for (let i = 0; i < lineSize; ++i) { series.push({ type: 'line', animation: false, coordinateSystem: 'polar', // polarIndex: 0, showSymbol: false, data: lines[i], itemStyle: { color: 'rgba(0,255,255,0.4)', width: 0.5 } }) }

详细的拟合过程和代码可以参考 在线示例

6、仪表盘

大屏仪表盘和默认仪表盘对比

这是 2 个仪表盘,右边的是 Echarts 默认的仪表盘,和右边的相比左边的明显更美观一些。那么怎么我们是怎么做到这些效果的呢?这个图主要分为内外两层,外层即仪表盘刻度部分,内层为环形柱状图部分(图中绿色部分)。

先说内层部分,内层主要由一个堆叠 Series Bar 组成,如下图红色标明的地方,绿色块(Bar类型1),绿色块间隔(Bar类型2),以及后面的留白部分(Bar类型3)其实都是堆叠Bar的一部分,在这里只需要计算好每个堆叠部分的数量值,然后设置各自的填充颜色让其堆叠即可。

生成 Series Bar 的关键代码:

const max = 100 const value = Math.floor(Math.random() * max) const breakSize = 3 const breakGap = 1 const panel = [] while (val > 0) { if (val >= breakSize + breakGap) { panel.push({ name: 'value', value: breakSize, itemStyle: { color: genColor(color2, 1, 1) } }) panel.push({ name: 'value', value: breakGap, itemStyle: { color: genColor(color, 0, 0) } }) val -= breakSize + breakGap } else if (val >= breakSize) { panel.push({ name: 'value', value: breakSize, itemStyle: { color: genColor(color2, 1, 1) } }) panel.push({ name: 'value', value: val - breakSize, itemStyle: { color: genColor(color, 0, 0) } }) val = 0 } else { panel.push({ name: 'value', value: val, itemStyle: { color: genColor(color2, 1, 1) } }) val = 0 } } panel.push({ name: 'pad', value: max - value > 0 ? max - value : 0, itemStyle: { color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [ { offset: 0, color: genColor(color, 0.1, 0.4) }, { offset: 1, color: genColor(color, 0.6, 0.4) }, ]) } })内层和外层结构剖析

外层部分,由于 Echarts 自带的单个仪表盘参数可设置范围有限。因此在外层部分,我们其实是由三个仪表盘合成起来组成一个仪表盘的。这三个仪表盘分别只有部分显示出来,构成了外层的这个大仪表盘。

const serise_1 = { type: 'gauge', detail: { show: false }, data: [{ value: value }], pointer: { show: false }, radius: "55%", startAngle: 210, endAngle: -30, // markArea: {label: {show: false}}, axisLine: { show: false, lineStyle: { color: [[1, new smartEF.echarts.graphic.LinearGradient(0, 1, 0, 0, [ { offset: 0, color: genColor(color, 0.1, 0.9) }, { offset: 1, color: genColor(color, 0.6, 0.9) }, ])]], width: 4 } }, splitLine: { show: false, }, axisTick: { show: false, }, axisLabel: { show: false } } const series_2 = { type: 'gauge', detail: { show: false }, data: [{ value: value }], pointer: { show: false }, radius: "57%", startAngle: 210, endAngle: -30, axisLine: { show: true, lineStyle: { color: [[1, genColor(color, 1, 0)]], width: 4 } }, splitLine: { show: true, length: 5, lineStyle: { color: genColor(color, 1, 1), width: 5, } }, axisTick: { show: false }, axisLabel: { show: false, } } const series_3 = { type: 'gauge', detail: { show: false }, data: [{ value: value }], pointer: { length: '53%' }, radius: "74%", startAngle: 210, endAngle: -30, axisLine: { show: true, lineStyle: { color: [[1, new smartEF.echarts.graphic.LinearGradient(0, 1, 0, 0, [ { offset: 0, color: genColor(color, 0.1, 0.9) }, { offset: 1, color: genColor(color, 0.6, 0.9) }, ])]], width: 4 } }, splitLine: { show: false, }, axisTick: { show: false, }, axisLabel: { show: true, distance: -18, color: genColor(color, 1, 1), fontFamily: 'VUI-Digital', fontSize: 24, } }

最后,在图表的最底端,有一个六边形。这个六边形放置在此处的设计可能是为了视觉色彩的平衡,实现的时候,是放置了一个空的雷达图在此处。

const series = [ series_1, series_2, series_3 ] // 别忘了把 Series Bar 也合到最后的 sereis 中 for (let i = 0; i < panel.length; ++i) { series.push({ name: panel[i].name, type: 'bar', data: [panel[i]], coordinateSystem: 'polar', barWidth: 30, animation: false, stack: '2', }) } const options = { title: 'title 配置', angleAxis: 'angleAxis 配置', radar: '雷达图配置', series: series }

详细配置项细节可以参考 在线示例

Echarts 已经做到了通用和易用,但是从通用需求到客户侧定制需求的距离需要的是开发者提高自身来缩短。

精力有限,本次分享先到此结束,如果您有更优雅的实现方式欢迎留言探讨,共同进步。

更多炫酷的大屏图表实践敬请期待 :)

最后,欢迎对数据可视化和可视分析有兴趣的小伙伴们参加 2019ChinaVIS 可视分析挑战赛,挑战赛详细信息可戳下面链接:



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3